package com.jrx.anytxn.common.uitl;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.*;
import org.springframework.core.ResolvableType;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: anytxn-cf-common
 * @description: 属性赋值转换器
 * @author: xiaoyu.jing
 * @date: 2019-10-13 14:27
 **/
public class PropertyValuesResolver {
    private Map<String, PropertyDescriptor> propertyDescCache = new ConcurrentHashMap<>();

    @SuppressWarnings({"unchecked", "rawtypes"})
    public PropertyValues resolve(Object data, Class<?> targetType) {
        MutablePropertyValues values = new MutablePropertyValues();
        if (null == data) {
            return values;
        }
        if ((data instanceof Map)) {
            Map<String, Object> dataMap = (Map<String, Object>) data;

            for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                PropertyDescriptor pd = findPropertyDesc(targetType, key);
                if (null == pd) {
                    continue;
                }
                Class propertyType = pd.getPropertyType();
                Method readMethod = pd.getReadMethod();
                if ((propertyType == null) || (readMethod == null)) {
                    continue;
                }
                values.addPropertyValues(resolve(key, value, propertyType, readMethod, 1));
            }
        }
        return values;
    }

    private PropertyDescriptor findPropertyDesc(Class<?> type, String propertyName) {
        String key = type.getName() + "." + propertyName;
        if (this.propertyDescCache.containsKey(key)) {
            return this.propertyDescCache.get(key);
        }
        return doFindPropertyDesc(type.getName(), type, propertyName);
    }

    @SuppressWarnings("rawtypes")
    private PropertyDescriptor doFindPropertyDesc(String key, Class<?> type, String propertyName) {
        if (PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName)) {
            int index = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyName);
            String currentProperty = propertyName.substring(0, index);
            PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(type, currentProperty);
            if (pd != null) {
                Class propertyType = pd.getPropertyType();
                if (null == propertyType) {
                    return null;
                }
                Method writeMethod = pd.getWriteMethod();
                if (null == writeMethod) {
                    return null;
                }
                key = key + "." + currentProperty;
                String subsequenceProperty = propertyName.substring(index + 1);
                return doFindPropertyDesc(key, propertyType, subsequenceProperty);
            }
            return null;
        }

        key = key + "." + propertyName;
        PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(type, propertyName);
        if (pd != null) {
            this.propertyDescCache.put(key, pd);
        }
        return pd;
    }

    private PropertyValues resolve(String path, Object data, Class<?> type, Method method, int level) {
        if ((null == data) || (null == type)) {
            return null;
        }
        MutablePropertyValues values = new MutablePropertyValues();
        if (BeanUtils.isSimpleProperty(type)) {
            values.add(path, data);
            return values;
        }
        if (List.class.isAssignableFrom(type)) {
            handleListType(path, values, data, method, level);
        } else if (Map.class.isAssignableFrom(type)) {
            handleMapType(path, values, data, method, level);
        } else {
            PropertyValues pvs = resolve(data, type);
            if (null != pvs) {
                PropertyValue[] pvArray = pvs.getPropertyValues();
                for (PropertyValue pv : pvArray) {
                    values.add(path + "." + pv.getName(), pv.getValue());
                }
            }
        }
        return values;
    }

    @SuppressWarnings("rawtypes")
    private PropertyValues handleListType(String path, MutablePropertyValues values, Object data, Method method,
                                          int level) {
        Class eleType = ResolvableType.forMethodReturnType(method).getNested(level).asCollection().resolveGeneric();
        if (null == eleType) {
            return values;
        }
        if ((data instanceof List)) {
            List dataList = (List) data;
            for (int i = 0; i < dataList.size(); i++) {
                Object element = dataList.get(i);
                String subPath = path + "[" + i + "]";
                values.addPropertyValues(resolve(subPath, element, eleType, method, level + 1));
            }
        }
        return values;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private PropertyValues handleMapType(String path, MutablePropertyValues values, Object data, Method method,
                                         int level) {
        Class eleType = ResolvableType.forMethodReturnType(method).getNested(level).asMap().resolveGeneric(1);

        if (null == eleType) {
            return values;
        }
        if ((data instanceof Map)) {
            Map<String, Object> dataMap = (Map<String, Object>) data;
            for (Map.Entry entry : dataMap.entrySet()) {
                String key = entry.getKey().toString();
                Object value = entry.getValue();
                String subPath = path + "[" + key + "]";
                values.addPropertyValues(resolve(subPath, value, eleType, method, level + 1));
            }
        }
        return values;
    }
}